/**
 * 版权所有(C)，上海海鼎信息工程股份有限公司，2009，所有权利保留。
 * 
 * 项目名：	rumba-widget2
 * 文件名：	RTextBoxBase.java
 * 模块说明：	
 * 修改历史：
 * Feb 2, 2009 - lxm - 创建。
 */
package com.hd123.rumba.gwt.widget2.client.form;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.dom.client.Document;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.HasAllFocusHandlers;
import com.google.gwt.event.dom.client.HasAllKeyHandlers;
import com.google.gwt.event.dom.client.HasChangeHandlers;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FocusListener;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasFocus;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.ListenerWrapper;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.SourcesChangeEvents;
import com.google.gwt.user.client.ui.SourcesClickEvents;
import com.google.gwt.user.client.ui.TextBoxBase;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.hd123.rumba.gwt.widget2.client.button.IsRButton;
import com.hd123.rumba.gwt.widget2.client.fielddef.FieldDef;
import com.hd123.rumba.gwt.widget2.client.fielddef.HasFieldDef;
import com.hd123.rumba.gwt.widget2.client.fielddef.StringFieldDef;
import com.hd123.rumba.gwt.widget2.client.form.RValidator.ValidResult;
import com.hd123.rumba.gwt.widget2.client.misc.RTextAlign;
import com.hd123.rumba.gwt.widget2.client.panel.FlowPanelHelper;
import com.hd123.rumba.gwt.widget2.client.tip.RTip;
import com.hd123.rumba.gwt.widget2.client.tip.RTipButton;
import com.hd123.rumba.gwt.widget2.client.tip.RTipListener;

/**
 * 文本输入框控件基类。
 * <p>
 * 
 * <b>控件元素结构</b><br>
 * div class=field<br>
 * ..[? class=input]<br>
 * ..[? class=buttons]<br>
 * ..[div class=suffix]<br>
 * ..[? class=tip]<br>
 * ..[div class=note]
 * <p>
 * 
 * <b>CSS Style Rules</b> <ol type=disc>
 * <li>.rb-TextBoxBase { div }</li>
 * <li>.rb-TextBoxBase-disabled { div }</li>
 * <li>.rb-TextBoxBase .input { 输入框 }</li>
 * <li>.rb-TextBoxBase .input-disabled { 当不可用状态时的输入框 }</li>
 * <li>.rb-TextBoxBase .input-readonly { 当只读状态时的输入框 }</li>
 * <li>.rb-TextBoxBase .input-focused { 当获得焦点状态时的输入框 }</li>
 * <li>.rb-TextBoxBase .input-error { 当提示错误时的输入框 }</li>
 * <li>.rb-TextBoxBase .input-warning { 当提示警告时的输入框 }</li>
 * <li>.rb-TextBoxBase .buttons {  }</li>
 * <li>.rb-TextBoxBase .suffix { div }</li>
 * <li>.rb-TextBoxBase .tip { div }</li>
 * <li>.rb-TextBoxBase .note {  }</li> </ol>
 * 
 * <p>
 * fieldDef.isRequired()和控件本身的isRequired()，谁后设置就以谁为准。
 * 
 * 
 * @author lxm
 * 
 */
@SuppressWarnings("deprecation")
public abstract class RTextBoxBase extends RFieldComposite implements HasText, HasFocus, Focusable,
    HasAllFocusHandlers, HasAllKeyHandlers, SourcesChangeEvents, HasChangeHandlers,
    SourcesClickEvents, HasClickHandlers, HasRValidator, HasFieldDef {

  private static final String STYLENAME_DEFAULT = "rb-TextBoxBase";

  /**
   * 创建对象实例。
   * 
   * @param input
   *          指定输入框控件对象。
   */
  public RTextBoxBase(TextBoxBase input) {
    super();

    if (input == null)
      throw new RuntimeException("参数input不可以为null。");

    if (input instanceof SourcesBeforeChangeEvents)
      ((SourcesBeforeChangeEvents) input).setBeforeChangeListener(new BeforeChangeListener() {
        public boolean beforeChange(Widget sender) {
          return beforeInputChange();
        }
      });

    drawSelf(input);
  }

  private FlowPanel pnl;
  private Container_pnlField pnlFieldParts;
  private TextBoxBase input;
  private RTip tip;
  private boolean empty = true;
  private RValidator validator;
  private FieldDef fieldDef;
  private ArrayList buttons = new ArrayList();

  private boolean focused = false;

  /** 输入为空时的显示文本。默认为""。 */
  private String emptyText = "";

  /** 正则表达式，用于验证输入文本是否正确。 */
  private String regex;

  /** 正则表达式检查错误后的提示文本信息，若为null则将显示标准文本。 */
  private String regexMessage;

  private static final String CNAME_INPUT = "input";
  private static final String[] CNAME_BUTTONS = new String[] {
      "button0", "button1", "button2", "button3" };
  private static final String CNAME_SUFFIX = "suffix";
  private static final String CNAME_TIP = "tip";
  private static final String CNAME_NOTE = "note";

  public void setEnabled(boolean enabled) {
    super.setEnabled(enabled);
    if (enabled) {
      DOM.removeElementAttribute(input.getElement(), "disabled");
    } else {
      DOM.setElementAttribute(input.getElement(), "disabled", "true");
    }
    refreshInputStyle();
    refreshEmptyText();
  }

  public void setRequired(boolean required) {
    super.setRequired(required);
    if (fieldDef != null)
      fieldDef.setNullable(!required);
  }

  /** 是否空 */
  protected boolean isEmpty() {
    return empty;
  }

  /** 是否空 */
  protected void setEmpty(boolean empty) {
    this.empty = empty;
  }

  /** 输入为空时的显示文本。默认为""。 */
  public String getEmptyText() {
    return emptyText;
  }

  /** 输入为空时的显示文本。默认为""。 */
  public void setEmptyText(String emptyText) {
    this.emptyText = emptyText == null ? "" : emptyText;
    refreshEmptyText();
  }

  /**
   * 根据当前状态刷新空值文本
   * 
   */
  protected void refreshEmptyText() {
    if (!isEmpty()) {
      getInput().removeStyleDependentName("empty");
      return;
    }

    if (!isEnabled() || isReadOnly()) {
      getInput().setText("");
      getInput().removeStyleDependentName("empty");
      return;
    }

    if (isFocused()) {
      getInput().setText("");
      getInput().removeStyleDependentName("empty");
    } else {
      getInput().setText(emptyText);
      getInput().addStyleDependentName("empty");
    }
  }

  /**
   * 取得文本输入框允许的最大输入长度（字符数）。
   * 
   * @return
   */
  protected int getMaxLength() {
    return DOM.getElementPropertyInt(getInput().getElement(), "maxLength");
  }

  /**
   * 设置文本输入框允许的最大输入长度（字符数）。
   * 
   * @param length
   */
  protected void setMaxLength(int length) {
    DOM.setElementPropertyInt(getInput().getElement(), "maxLength", length);
  }

  /**
   * 取消maxLength的设置
   * 
   */
  protected void removeMaxLength() {
    DOM.removeElementAttribute(getInput().getElement(), "maxLength");
  }

  /** 正则表达式，用于验证输入文本是否正确。 */
  public String getRegex() {
    return regex;
  }

  /** 正则表达式，用于验证输入文本是否正确。 */
  public void setRegex(String regex) {
    this.regex = regex;
  }

  /** 正则表达式检查错误后的提示文本信息，若为null则将显示标准文本。 */
  public String getRegexMessage() {
    return regexMessage;
  }

  /** 正则表达式检查错误后的提示文本信息，若为null则将显示标准文本。 */
  public void setRegexMessage(String regexMessage) {
    this.regexMessage = regexMessage;
  }

  /**
   * 取得后缀。
   * 
   * @return
   */
  public String getSuffix() {
    if (!pnlFieldParts.isCreated(CNAME_SUFFIX))
      return null;
    HTML html = (HTML) pnlFieldParts.getChild(CNAME_SUFFIX);
    return html.getHTML();
  }

  /**
   * 设置后缀。
   * 
   * @param suffix
   */
  public void setSuffix(String suffix) {
    HTML html = (HTML) pnlFieldParts.getChild(CNAME_SUFFIX);
    html.setHTML(suffix == null ? "" : suffix);
  }

  /**
   * 取得说明。
   * 
   * @return
   */
  public String getNote() {
    if (!pnlFieldParts.isCreated(CNAME_NOTE))
      return null;
    HTML html = (HTML) pnlFieldParts.getChild(CNAME_NOTE);
    return html.getHTML();
  }

  /**
   * 返回说明。
   * 
   * @param note
   */
  public void setNote(String note) {
    HTML html = (HTML) pnlFieldParts.getChild(CNAME_NOTE);
    html.setHTML(note == null ? "" : note);
  }

  /**
   * 清除所有已经被加入的按钮。
   */
  public void clearButtons() {
    buttons.clear();
    for (int i = 0, n = CNAME_BUTTONS.length; i < n; i++) {
      if (pnlFieldParts.isCreated(CNAME_BUTTONS[i]) == false)
        continue;
      SimplePanel pnl = (SimplePanel) pnlFieldParts.getChild(CNAME_BUTTONS[i]);
      pnl.setWidget(null);
    }
  }

  /**
   * 添加一个按钮。最多允许添加4个按钮。
   * 
   * @param button
   *          指定被添加的按钮控件对象。
   */
  public void addButton(IsRButton button) {
    if (button == null)
      return;
    if (buttons.contains(button))
      return;
    int nextIdx = buttons.size();
    if (nextIdx >= CNAME_BUTTONS.length)
      throw new RuntimeException("最多允许添加" + CNAME_BUTTONS.length + "个按钮。");
    button.setShowText(false);
    if (button instanceof Focusable)
      ((Focusable) button).setTabIndex(-1);
    DOM.setStyleAttribute(((Widget) button).getElement(), "float", "left");
    SimplePanel pnl = (SimplePanel) pnlFieldParts.getChild(CNAME_BUTTONS[nextIdx]);
    pnl.setWidget((Widget) button);
    buttons.add(button);
  }

  /**
   * 取得作为输入框的Widget。
   * 
   * @return
   */
  protected TextBoxBase getInput() {
    return input;
  }

  /**
   * 设置输入框的宽度。
   * 
   * @param width
   */
  public void setInputWidth(String width) {
    input.setWidth(width);
  }

  public String getText() {
    if (isEmpty())
      return "";
    else
      return getInput().getText();
  }

  public void setText(String text) {
    setEmpty(text == null || "".equals(text));
    if (isEmpty())
      getInput().setText(getEmptyText());
    else
      getInput().setText(text);
    refreshEmptyText();
  }

  public int getTabIndex() {
    return input.getTabIndex();
  }

  public void setTabIndex(int index) {
    input.setTabIndex(index);
  }

  public void setAccessKey(char key) {
    input.setAccessKey(key);
  }

  public void setFocus(boolean focused) {
    input.setFocus(focused);
  }

  /**
   * @deprecated 请用{@link #addFocusHandler}和
   *             {@link #addBlurHandler}代替
   */
  @Deprecated
  public void addFocusListener(FocusListener listener) {
    ListenerWrapper.WrappedFocusListener.add(this, listener);
  }

  /**
   * @deprecated 请用{@link #addFocusHandler}和
   *             {@link #addBlurHandler}返回的
   *             {@link HandlerRegistration#removeHandler}代替
   */
  @Deprecated
  public void removeFocusListener(FocusListener listener) {
    ListenerWrapper.WrappedFocusListener.remove(this, listener);
  }

  public HandlerRegistration addFocusHandler(FocusHandler handler) {
    return addDomHandler(handler, FocusEvent.getType());
  }

  public HandlerRegistration addBlurHandler(BlurHandler handler) {
    return addDomHandler(handler, BlurEvent.getType());
  }

  /**
   * @deprecated 请用{@link #addKeyUpHandler}、
   *             {@link #addKeyDownHandler}和
   *             {@link #addKeyPressHandler} 代替
   */
  @Deprecated
  public void addKeyboardListener(KeyboardListener listener) {
    ListenerWrapper.WrappedKeyboardListener.add(this, listener);
  }

  /**
   * @deprecated 请用{@link #addKeyUpHandler}、
   *             {@link #addKeyDownHandler}和
   *             {@link #addKeyPressHandler}返回的
   *             {@link HandlerRegistration#removeHandler}代替
   */
  @Deprecated
  public void removeKeyboardListener(KeyboardListener listener) {
    ListenerWrapper.WrappedKeyboardListener.remove(this, listener);
  }

  public HandlerRegistration addKeyUpHandler(KeyUpHandler handler) {
    return addDomHandler(handler, KeyUpEvent.getType());
  }

  public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
    return addDomHandler(handler, KeyDownEvent.getType());
  }

  public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
    return addDomHandler(handler, KeyPressEvent.getType());
  }

  /**
   * @deprecated 请用{@link #addChangeHandler}代替
   */
  @Deprecated
  public void addChangeListener(ChangeListener listener) {
    ListenerWrapper.WrappedChangeListener.add(this, listener);
  }

  /**
   * @deprecated 请用{@link #addChangeHandler}返回的
   *             {@link HandlerRegistration#removeHandler}代替
   */
  @Deprecated
  public void removeChangeListener(ChangeListener listener) {
    ListenerWrapper.WrappedChangeListener.remove(this, listener);
  }

  public HandlerRegistration addChangeHandler(ChangeHandler handler) {
    return addDomHandler(handler, ChangeEvent.getType());
  }

  /**
   * 触发change事件
   * 
   */
  protected void fireChange() {
    if (beforeInputChange()) {
      DomEvent.fireNativeEvent(Document.get().createChangeEvent(), this);
    }
  }

  /**
   * @deprecated 请用{@link #addClickHandler}代替
   */
  @Deprecated
  public void addClickListener(ClickListener listener) {
    ListenerWrapper.WrappedClickListener.add(this, listener);
  }

  /**
   * @deprecated 请用{@link #addClickHandler}返回的
   *             {@link HandlerRegistration#removeHandler}代替
   */
  @Deprecated
  public void removeClickListener(ClickListener listener) {
    ListenerWrapper.WrappedClickListener.remove(this, listener);
  }

  public HandlerRegistration addClickHandler(ClickHandler handler) {
    return addDomHandler(handler, ClickEvent.getType());
  }

  public void cancelKey() {
    input.cancelKey();
  }

  public void setKey(char key) {
    input.setKey(key);
  }

  public int getCursorPos() {
    return input.getCursorPos();
  }

  public String getSelectedText() {
    return input.getSelectedText();
  }

  public int getSelectionLength() {
    return input.getSelectionLength();
  }

  public void selectAll() {
    input.selectAll();
  }

  public void setCursorPos(int pos) {
    input.setCursorPos(pos);
  }

  public void setSelectionRange(int pos, int length) {
    input.setSelectionRange(pos, length);
  }

  public boolean isReadOnly() {
    return input.isReadOnly();
  }

  public void setReadOnly(boolean readOnly) {
    input.setReadOnly(readOnly);
    refreshInputStyle();
    refreshEmptyText();
  }

  public void setTextAlignment(RTextAlign textAlign) {
    if (textAlign == null)
      return;
    input.setTextAlignment(textAlign.toTextAlign());
  }

  /**
   * 取得控件所包含的提示信息对象。
   * 
   * @return
   */
  public RTip getTip() {
    if (!pnlFieldParts.isCreated(CNAME_TIP)) {
      RTipButton btn = (RTipButton) pnlFieldParts.getChild(CNAME_TIP);
      btn.bind(tip);
    }
    return tip;
  }

  public RValidator getValidator() {
    return validator;
  }

  public void setValidator(RValidator validator) {
    this.validator = validator;
  }

  public void clearValidResults() {
    if (!pnlFieldParts.isCreated(CNAME_TIP))
      return;
    getTip().clear();
    refreshInputStyle();
  }

  public boolean isValid() {
    if (!pnlFieldParts.isCreated(CNAME_TIP))
      return true;
    return getTip().isValid();
  }

  public List getInvalidMessages() {
    List list = new ArrayList();
    if (!isValid())
      list.add(getTip().getDisplayed().toMessageRecord(this));
    return list;
  }

  public boolean validate() {
    if (isReadOnly() || !isEnabled() || !isVisible())
      return true;

    getTip().clear();

    if (fieldDef == null && isRequired() && (getText() == null || "".equals(getText().trim()))) {
      getTip().addError(getTipPrefix() + "不可以为空。");
    }

    if (regex != null) {
      if (!getText().matches(regex)) {
        String errMsg = regexMessage != null ? regexMessage : "输入值不符合指定的正则表达式(" + regex + ")。";
        getTip().addError(getTipPrefix() + errMsg);
      }
    }

    onValidate();

    if (validator != null) {
      ValidResult validResult = validator.validate(this, getText());
      getTip().add(validResult);
    }

    refreshInputStyle();
    return isValid();
  }

  public FieldDef getFieldDef() {
    return fieldDef;
  }

  public void setFieldDef(FieldDef fieldDef) {
    this.fieldDef = fieldDef == null ? null : fieldDef.clone();
    if (this.fieldDef == null)
      return;

    setFieldCaption(this.fieldDef.getCaption());
    setRequired(this.fieldDef.isRequired());

    if (fieldDef instanceof StringFieldDef) {
      setMaxLength(((StringFieldDef) fieldDef).getMaxLength());
    }

    setValidator(new RValidatorOnFieldDef(this.fieldDef));
  }

  /**
   * 提供给子类，用于扩展验证过程。
   */
  protected void onValidate() {
    // Do Nothing;
  }

  protected String getTipPrefix() {
    return getFieldCaption() == null ? "" : getFieldCaption() + "：";
  }

  /**
   * 取得当前是否处于获得焦点的状态。
   * 
   * @return
   */
  protected boolean isFocused() {
    return focused;
  }

  /**
   * 当按下回车键后被调用。提供给子类继承。
   * 
   * @return 返回被替换的keyCode。
   */
  protected int onKeyEnter() {
    return KeyCodes.KEY_TAB;
  }

  /**
   * 在输入框change事件触发前要做的事情，以便在change事件触发前进行合法性检查以及相关动作。
   * <p>
   * 本方法和input.addChangeListener的区别在于本方法保证在所有ChangeListeners之前执行，
   * 而 input.addChangeListener则无法保证这一点，
   * 因为观察者模式并不保证观察者被通知的顺序和当初被添加的顺序一致。
   * 
   * @return 是否触发change事件
   */
  protected boolean beforeInputChange() {
    setEmpty("".equals(getInput().getText()));
    return validate();
  }

  private void drawSelf(TextBoxBase input) {
    pnl = new FlowPanel();

    pnlFieldParts = new Container_pnlField();

    this.input = input;
    InputHandler inputHandler = new InputHandler();
    this.input.addFocusHandler(inputHandler);
    this.input.addBlurHandler(inputHandler);
    this.input.addKeyDownHandler(inputHandler);
    this.input.addChangeHandler(inputHandler);

    UIObject.setStyleName(this.input.getElement(), "input");
    pnl.add(this.input);

    for (int i = 0, n = CNAME_BUTTONS.length; i < n; i++)
      pnlFieldParts.appendChild(CNAME_BUTTONS[i], true);
    pnlFieldParts.appendChild(CNAME_SUFFIX, true);
    pnlFieldParts.appendChild(CNAME_TIP, true);
    tip = new RTip();
    tip.addTipListener(new Listener_tip());
    pnlFieldParts.appendChild(CNAME_NOTE, true);

    initWidget(pnl);
    setStyleName(STYLENAME_DEFAULT);
  }

  private void setFocused(boolean focused) {
    this.focused = focused;
    refreshInputStyle();
  }

  /**
   * 刷新input的样式，相关的状态包括：{@link #isEnabled()}、
   * {@link #isReadOnly()}、 {@link #focused}、 {@link #isValid()}。
   */
  protected void refreshInputStyle() {
    String newStyle = "input";
    if (!isEnabled()) {
      newStyle += " input-disabled";
    } else {
      if (isReadOnly())
        newStyle += " input-readonly";
      if (focused)
        newStyle += " input-focused";
      if (!isValid()) {
        newStyle += " input-" + getTip().getDisplayed().getLevel().getName();
      }
    }
    input.setStyleName(newStyle);
  }

  private class Container_pnlField extends FlowPanelHelper {
    public Container_pnlField() {
      super(pnl);
    }

    protected Widget createChild(String childName) {
      if (CNAME_INPUT.equals(childName)) {
        return input;
      }

      for (int i = 0, n = CNAME_BUTTONS.length; i < n; i++)
        if (CNAME_BUTTONS[i].equals(childName)) {
          SimplePanel pnl = new SimplePanel();
          pnl.setStyleName("buttons");
          return pnl;
        }

      if (CNAME_SUFFIX.equals(childName)) {
        HTML html = new HTML();
        html.setStyleName("suffix");
        return html;
      }

      if (CNAME_TIP.equals(childName)) {
        RTipButton tipButton = new RTipButton();
        return tipButton;
      }

      if (CNAME_NOTE.equals(childName)) {
        HTML html = new HTML();
        html.setStyleName("note");
        return html;
      }
      return null;
    }
  }

  /**
   * 文本框事件处理
   * 
   * @author LiQi
   * 
   */
  private class InputHandler implements FocusHandler, BlurHandler, KeyDownHandler, ChangeHandler {

    public void onFocus(FocusEvent event) {
      setFocused(true);
      refreshEmptyText();
      RTextBoxBase.this.fireEvent(event);
    }

    public void onBlur(BlurEvent event) {
      setFocused(false);
      refreshEmptyText();
      RTextBoxBase.this.fireEvent(event);
    }

    public void onKeyDown(KeyDownEvent event) {
      // Enter To Tab
      if (event.getNativeKeyCode() == '\r' || event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
        input.setKey((char) onKeyEnter());
    }

    public void onChange(ChangeEvent event) {
      // IE中change事件不会冒泡，而chrome中会冒泡。为统一行为，这里先主动触发父控件的事件，再禁止冒泡。
      RTextBoxBase.this.fireEvent(event);
      event.stopPropagation();
    }
  }

  private class Listener_tip implements RTipListener {
    public void onChanged(RTip sender) {
      refreshInputStyle();
    }
  }

}
